#include <stdlib.h>
#include <jni.h>
#include <android/log.h>
#include "lib/drivers/pvmdriver_AndroideAudioTrack_jni.h"

extern int fillBufferFromAudioTrackDevice(void* device, void* buffer, int len);

jmethodID findClassStaticMethod(JNIEnv* env, jclass* cls, const char* className, const char* methodName, const char* methodSignature);
jmethodID findClassMethod(JNIEnv* env, jclass* cls, const char* className, const char* methodName, const char* methodSignature);

JavaVM* myVm = NULL;

JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved)
{
	myVm = vm;
	return JNI_VERSION_1_6; 
}

void callSystemGC()
{
	JNIEnv* env = NULL;

	if(myVm==NULL)
	{
		return;
	}
	jint jniRes = (*myVm)->GetEnv(myVm, (void**)&env, JNI_VERSION_1_2);
	if(jniRes!=JNI_OK)
	{
		return;
	}
	jclass cls = NULL;
	jmethodID mid = findClassStaticMethod(env, &cls, "java/lang/System", "gc", "()V");
	if(mid == NULL)
	{
		(*env)->DeleteLocalRef(env, cls);
		return;
	}

	(*env)->CallStaticVoidMethod(env, cls, mid);
}

JNIEXPORT jint JNICALL 
Java_pvm_audiotrackdriver_AudioTrackDriver_nativeDriveFillBuffer(JNIEnv* env,
                                                  jobject thiz, jint param, jobject buf)
{
	void *bufPtr;
	int len;

	if(param==0)
		return 0;
	if(env==NULL || thiz==NULL || buf==NULL)
		return 0;

	len = (*env)->GetDirectBufferCapacity(env,  buf);
	if(len<0)
		return 0;
	bufPtr = (*env)->GetDirectBufferAddress(env,  buf);
	return fillBufferFromAudioTrackDevice((void*)param, bufPtr, len);
}

jmethodID findClassStaticMethod(JNIEnv* env, jclass* cls, const char* className, const char* methodName, const char* methodSignature)
{
	if(env == NULL)
	{
		if(myVm==NULL)
		{
			__android_log_print(ANDROID_LOG_DEBUG, "pvmdriver_AndroideAudioTrack_jni.c", "myVm is not valid!\n");
			return NULL;
		}

		jint jniRes = (*myVm)->GetEnv(myVm, (void**)&env, JNI_VERSION_1_2);
		if(jniRes!=JNI_OK)
		{
			__android_log_print(ANDROID_LOG_DEBUG, "pvmdriver_AndroideAudioTrack_jni.c", "GetEnv failed with code %d\n", jniRes);
			return NULL;
		}
	}

	*cls = (*env)->FindClass(env, className); 
	if(*cls == NULL)
	{
		__android_log_print(ANDROID_LOG_DEBUG, "pvmdriver_AndroideAudioTrack_jni.c", "Can't find class %s\n", className);
		return NULL;
	}

	jmethodID mid = (*env)->GetStaticMethodID(env, *cls, methodName, methodSignature);
	if (mid == 0)
	{
		__android_log_print(ANDROID_LOG_DEBUG, "pvmdriver_AndroideAudioTrack_jni.c", "Can't find method %s - %s\n", methodName, methodSignature);
		(*env)->DeleteLocalRef(env, *cls);
		*cls = NULL;
		return NULL;
	}

	return mid;
}

jmethodID findClassMethod(JNIEnv* env, jclass* cls, const char* className, const char* methodName, const char* methodSignature)
{
	if(env == NULL)
	{
		if(myVm==NULL)
		{
			__android_log_print(ANDROID_LOG_DEBUG, "pvmdriver_AndroideAudioTrack_jni.c", "myVm is not valid!\n");
			return NULL;
		}

		jint jniRes = (*myVm)->GetEnv(myVm, (void**)&env, JNI_VERSION_1_2);
		if(jniRes!=JNI_OK)
		{
			__android_log_print(ANDROID_LOG_DEBUG, "pvmdriver_AndroideAudioTrack_jni.c", "GetEnv failed with code %d\n", jniRes);
			return NULL;
		}
	}

	*cls = (*env)->FindClass(env, className); 
	if(*cls==NULL)
	{
		__android_log_print(ANDROID_LOG_DEBUG, "pvmdriver_AndroideAudioTrack_jni.c", "Can't find class %s\n", className);
		return NULL;
	}

	jmethodID mid = (*env)->GetMethodID(env, *cls, methodName, methodSignature);
	if (mid == 0)
	{
		__android_log_print(ANDROID_LOG_DEBUG, "pvmdriver_AndroideAudioTrack_jni.c", "Can't find method %s - %s\n", methodName, methodSignature);
		(*env)->DeleteLocalRef(env, *cls);
		*cls = NULL;
		return NULL;
	}

	return mid;
}

void* pvmCreateJavaDriver(void* param, int sampleRate, int bitsPerSample, int stereo, int bufferLengthInMS)
{
	JNIEnv* env = NULL;

	if(myVm==NULL)
	{
		__android_log_print(ANDROID_LOG_DEBUG, "pvmdriver_AndroideAudioTrack_jni.c", "myVm is not valid!\n");
		return NULL;
	}

	jint jniRes = (*myVm)->GetEnv(myVm, (void**)&env, JNI_VERSION_1_2);
	if(jniRes!=JNI_OK)
	{
		__android_log_print(ANDROID_LOG_DEBUG, "pvmdriver_AndroideAudioTrack_jni.c", "GetEnv failed with code %d\n", jniRes);
		return NULL;
	}

	jclass cls = NULL;
	jmethodID mid = findClassStaticMethod(env, &cls, "pvm/audiotrackdriver/AudioTrackDriver", "createDriver", "(IIIZI)Lpvm/audiotrackdriver/AudioTrackDriver;");
	if(mid == NULL)
	{
		(*env)->DeleteLocalRef(env, cls);
		return NULL;
	}

	jobject javaDriver = (*env)->CallStaticObjectMethod(env, cls, mid, (jint)param, (jint)sampleRate, (jint)bitsPerSample, (jboolean)stereo, (jint)bufferLengthInMS);

	(*env)->DeleteLocalRef(env, cls);
	cls = NULL;

	if(javaDriver==NULL)
	{
		__android_log_print(ANDROID_LOG_DEBUG, "pvmdriver_AndroideAudioTrack_jni.c", "createDriver failed\n");
		return NULL;
	}

	jobject javaDriverGlobalRef = (*env)->NewGlobalRef(env, javaDriver);
	(*env)->DeleteLocalRef(env, javaDriver);
	javaDriver = NULL;

	if(javaDriverGlobalRef==NULL)
	{
		__android_log_print(ANDROID_LOG_DEBUG, "pvmdriver_AndroideAudioTrack_jni.c", "createDriver (NewGlobalRef) failed\n");
		return NULL;
	}

	return javaDriverGlobalRef;
}

void destroyJavaDriver(void* javaDriverPtr)
{
	if(javaDriverPtr==NULL)
		return;

	JNIEnv* env = NULL;
	jobject javaDriver = (jobject)javaDriverPtr;
	jint jniRes = (*myVm)->GetEnv(myVm, (void**)&env, JNI_VERSION_1_2);
	if(jniRes!=JNI_OK)
	{
		return;
	}

	jclass cls = NULL;
	jmethodID mid = findClassMethod(env, &cls, "pvm/audiotrackdriver/AudioTrackDriver", "stopSoundThread", "()V");
	if(mid == NULL)
	{
		return;
	}
	(*env)->CallVoidMethod(env, (jobject)javaDriver, mid);
	(*env)->DeleteLocalRef(env, cls);
	cls = NULL;
	(*env)->DeleteGlobalRef(env, javaDriver);
	javaDriver = NULL;
	javaDriverPtr = NULL;
	callSystemGC();
}

int javaDriverGetCurrentBufferLengthMS(void* javaDriver)
{
	if(javaDriver==NULL)
		return 0;

	JNIEnv* env = NULL;

	if(myVm==NULL)
	{
		__android_log_print(ANDROID_LOG_DEBUG, "pvmdriver_AndroideAudioTrack_jni.c", "myVm is not valid!\n");
		return 0;
	}

	jint jniRes = (*myVm)->GetEnv(myVm, (void**)&env, JNI_VERSION_1_2);
	if(jniRes!=JNI_OK)
	{
		__android_log_print(ANDROID_LOG_DEBUG, "pvmdriver_AndroideAudioTrack_jni.c", "GetEnv failed with code %d\n", jniRes);
		return 0;
	}

	jclass cls = NULL;
	jmethodID mid = findClassStaticMethod(env, &cls, "pvm/audiotrackdriver/AudioTrackDriver", "getCurrentBufferLengthMS", "()I");
	if(mid == NULL)
	{
		return 0;
	}
	jint retVal = (*env)->CallIntMethod(env, (jobject)javaDriver, mid);
	(*env)->DeleteLocalRef(env, cls);
	cls = NULL;

	return (int)retVal;
}

int javaDriverGetCurrentBufferLengthInBytes(void* javaDriver)
{
	if(javaDriver==NULL)
		return 0;

	JNIEnv* env = NULL;

	if(myVm==NULL)
	{
		__android_log_print(ANDROID_LOG_DEBUG, "pvmdriver_AndroideAudioTrack_jni.c", "myVm is not valid!\n");
		return 0;
	}

	jint jniRes = (*myVm)->GetEnv(myVm, (void**)&env, JNI_VERSION_1_2);
	if(jniRes!=JNI_OK)
	{
		__android_log_print(ANDROID_LOG_DEBUG, "pvmdriver_AndroideAudioTrack_jni.c", "GetEnv failed with code %d\n", jniRes);
		return 0;
	}
	jclass cls = NULL;
	jmethodID mid = findClassMethod(env, &cls, "pvm/audiotrackdriver/AudioTrackDriver", "getCurrentBufferLengthInBytes", "()I");
	if(mid == NULL)
	{
		return 0;
	}
	jint retVal = (*env)->CallIntMethod(env, (jobject)javaDriver, mid);
	(*env)->DeleteLocalRef(env, cls);
	cls = NULL;

	return (int)retVal;
}

void javaDriverSetCurrentBufferLengthMS(void* javaDriver, int newLength)
{
	if(javaDriver==NULL)
		return;

	JNIEnv* env = NULL;

	if(myVm==NULL)
	{
		__android_log_print(ANDROID_LOG_DEBUG, "pvmdriver_AndroideAudioTrack_jni.c", "myVm is not valid!\n");
		return;
	}

	jint jniRes = (*myVm)->GetEnv(myVm, (void**)&env, JNI_VERSION_1_2);
	if(jniRes!=JNI_OK)
	{
		__android_log_print(ANDROID_LOG_DEBUG, "pvmdriver_AndroideAudioTrack_jni.c", "GetEnv failed with code %d\n", jniRes);
		return;
	}
	jclass cls = NULL;
	jmethodID mid = findClassMethod(env, &cls, "pvm/audiotrackdriver/AudioTrackDriver", "changeBufferLength", "(I)V");

	if(mid == NULL)
	{
		return;
	}

	(*env)->CallVoidMethod(env, (jobject)javaDriver, mid, (jint)newLength);

	(*env)->DeleteLocalRef(env, cls);
	cls = NULL;
}


void javaDriverSuspendDevice(void* javaDriver)
{
	if(javaDriver==NULL)
		return;

	JNIEnv* env = NULL;

	if(myVm==NULL)
	{
		__android_log_print(ANDROID_LOG_DEBUG, "pvmdriver_AndroideAudioTrack_jni.c", "myVm is not valid!\n");
		return;
	}

	jint jniRes = (*myVm)->GetEnv(myVm, (void**)&env, JNI_VERSION_1_2);
	if(jniRes!=JNI_OK)
	{
		__android_log_print(ANDROID_LOG_DEBUG, "pvmdriver_AndroideAudioTrack_jni.c", "GetEnv failed with code %d\n", jniRes);
		return;
	}
	jclass cls = NULL;
	jmethodID mid = findClassMethod(env, &cls, "pvm/audiotrackdriver/AudioTrackDriver", "suspendDevice", "()V");

	if(mid == NULL)
	{
		return;
	}

	(*env)->CallVoidMethod(env, (jobject)javaDriver, mid);

	(*env)->DeleteLocalRef(env, cls);
	cls = NULL;
}

void javaDriverResumeDevice(void* javaDriver)
{
	if(javaDriver==NULL)
		return;

	JNIEnv* env = NULL;

	if(myVm==NULL)
	{
		__android_log_print(ANDROID_LOG_DEBUG, "pvmdriver_AndroideAudioTrack_jni.c", "myVm is not valid!\n");
		return;
	}

	jint jniRes = (*myVm)->GetEnv(myVm, (void**)&env, JNI_VERSION_1_2);
	if(jniRes!=JNI_OK)
	{
		__android_log_print(ANDROID_LOG_DEBUG, "pvmdriver_AndroideAudioTrack_jni.c", "GetEnv failed with code %d\n", jniRes);
		return;
	}
	jclass cls = NULL;
	jmethodID mid = findClassMethod(env, &cls, "pvm/audiotrackdriver/AudioTrackDriver", "resumeDevice", "()V");

	if(mid == NULL)
	{
		return;
	}

	(*env)->CallVoidMethod(env, (jobject)javaDriver, mid);

	(*env)->DeleteLocalRef(env, cls);
	cls = NULL;
}

void javaDriverNotifyDevice(void* javaDriver)
{
	if(javaDriver==NULL)
		return;

	JNIEnv* env = NULL;

	if(myVm==NULL)
	{
		__android_log_print(ANDROID_LOG_DEBUG, "pvmdriver_AndroideAudioTrack_jni.c", "myVm is not valid!\n");
		return;
	}

	jint jniRes = (*myVm)->GetEnv(myVm, (void**)&env, JNI_VERSION_1_2);
	if(jniRes!=JNI_OK)
	{
		__android_log_print(ANDROID_LOG_DEBUG, "pvmdriver_AndroideAudioTrack_jni.c", "GetEnv failed with code %d\n", jniRes);
		return;
	}
	jclass cls = NULL;
	jmethodID mid = findClassMethod(env, &cls, "pvm/audiotrackdriver/AudioTrackDriver", "notifyThread", "()V");

	if(mid == NULL)
	{
		return;
	}

	(*env)->CallVoidMethod(env, (jobject)javaDriver, mid);

	(*env)->DeleteLocalRef(env, cls);
	cls = NULL;
}

int javaDriverIsDeviceSuspended(void* javaDriver)
{
	if(javaDriver==NULL)
		return -1;

	JNIEnv* env = NULL;

	if(myVm==NULL)
	{
		__android_log_print(ANDROID_LOG_DEBUG, "pvmdriver_AndroideAudioTrack_jni.c", "myVm is not valid!\n");
		return -1;
	}

	jint jniRes = (*myVm)->GetEnv(myVm, (void**)&env, JNI_VERSION_1_2);
	if(jniRes!=JNI_OK)
	{
		__android_log_print(ANDROID_LOG_DEBUG, "pvmdriver_AndroideAudioTrack_jni.c", "GetEnv failed with code %d\n", jniRes);
		return -1;
	}
	jclass cls = NULL;
	jmethodID mid = findClassMethod(env, &cls, "pvm/audiotrackdriver/AudioTrackDriver", "isDeviceSuspended", "()Z");

	if(mid == NULL)
	{
		return -1;
	}

	jboolean retVal = (*env)->CallBooleanMethod(env, (jobject)javaDriver, mid);

	(*env)->DeleteLocalRef(env, cls);
	cls = NULL;

	if(retVal)
		return 1;

	return 0;
}